﻿// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they bagin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
// 
// - Sun Tsu,
// "The Art of War"

using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using log4net.Util;

namespace Infrastructure.Logging
{
    public static class LogManager
    {
        #region Data members

        /// <summary>
        /// flag to indicate if the logger is already configured
        /// </summary>
        private static bool sIsConfigured;

        /// <summary>
        /// flag to indicate if log4net internal debug is active.
        /// </summary>
        private static readonly bool sDebugEnabled;

        #endregion

        /// <summary>
        /// load log4net internal debuging flag.
        /// </summary>
        static LogManager()
        {
            try
            {
                var debug = ConfigurationManager.AppSettings["log4net.Internal.Debug"];
                bool.TryParse( debug, out sDebugEnabled );
                Configure();
            }
            catch( Exception ex )
            {
                WriteTrace( "Failed to read app config: " + ex.Message );
            }
        }

        /// <summary>
        /// Get logger by name.
        /// always return the same logger for the same name, create on first call.
        /// </summary>
        /// <param name="name">name</param>
        /// <returns>logger for the given name</returns>
        public static ILog GetLogger( string name )
        {
            return new Logger( log4net.LogManager.GetLogger( name ).Logger);
        }

        /// <summary>
        /// Get logger by type.
        /// always return the same logger for the same type, create on first call.
        /// </summary>
        /// <param name="type">type</param>
        /// <returns>logger for the given type</returns>
        public static ILog GetLogger(Type type)
        {
            return new Logger(log4net.LogManager.GetLogger(type).Logger);
        }

        /// <summary>
        /// Configure the log4net.
        /// - If the AppSettings contains a key "log4net" it will try to configure with
        ///   the the value, it should be file path.
        /// - Otherwise it will try to configure with the app.config of the application.
        /// Watch is set on the file to update on file change.
        /// </summary>
        /// <remarks>
        /// if configuration fails, no exception is thrown.
        /// </remarks>
        public static void Configure()
        {
            try
            {
                if( !sIsConfigured )
                {
                    InitLog4NetInnerLogs();
                    InitConfig();
                    sIsConfigured = true;
                }
            }
            catch( Exception )
            {
                WriteTrace("Failed loading configuration, no configuration loaded");
            }
        }

        /// <summary>
        /// Init log4net internal debug to write to log file that is in "log4net.Internal.Debug.TraceFile"
        /// </summary>
        private static void InitLog4NetInnerLogs()
        {
            try
            {
                if( sDebugEnabled )
                {
                    string traceFile = ConfigurationManager.AppSettings["log4net.Internal.Debug.TraceFile"];
                    traceFile = ConvertToFullPath( traceFile );

                    if( traceFile != null )
                    {
                        Directory.CreateDirectory( Path.GetDirectoryName( traceFile ) );
                        if( File.Exists( traceFile ) )
                            File.Delete( traceFile );

                        var trace = new TextWriterTraceListener( traceFile );
                        Trace.Listeners.Add( trace );
                        Trace.AutoFlush = true;
                    }
                }
            }
            catch( Exception ex )
            {
                WriteTrace( "Failed tp configure Log4net internal logs: " + ex.Message );
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private static void InitConfig()
        {
            string configRedirect = ConfigurationManager.AppSettings["log4net"];

            if( configRedirect != null )
            {
                if (!Path.IsPathRooted(configRedirect) && !File.Exists(configRedirect))
                {
                    WriteTrace( "log4net redirect file not found in local path [" + configRedirect + "]" );
                    configRedirect = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, configRedirect );
                }

                WriteTrace( "Attempt: " + configRedirect );
                Configure( new FileInfo( configRedirect ) );
            }
            else
            {
                try
                {
                    WriteTrace( "Attempt load exe configuration..." );
                    string appConfigFilePath = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
                    FileInfo appConfig = new FileInfo( appConfigFilePath );
                    Configure( appConfig );
                }
                catch (Exception)
                {
                    WriteTrace("Failed open exe configuration, try open without watch...");
                    log4net.Config.XmlConfigurator.Configure();
                }
            }
        }

        /// <summary>
        /// configure the log4net with the given file configuration.
        /// Watch is set on the file to update on file change.
        /// </summary>
        /// <remarks>
        /// if configuration fails, no exception is thrown.
        /// </remarks>
        /// <param name="configFile">the file with log4net configuration</param>
        public static void Configure( FileInfo configFile )
        {
            log4net.Config.XmlConfigurator.ConfigureAndWatch( configFile );
        }

        /// <summary>
        /// write the given value to trace iff log4net internal debug is active.
        /// </summary>
        /// <param name="value">the value to write to trace</param>
        internal static void WriteTrace( object value )
        {
            if( sDebugEnabled )
                Trace.WriteLine( value );
        }

        /// <summary>
        /// Convert a path into a fully qualified local file path.
        /// </summary>
        /// <param name="path">The path to convert.</param>
        /// <returns>The fully qualified path.</returns>
        /// <remarks>
        /// <para>
        /// Converts the path specified to a fully
        /// qualified path. If the path is relative it is
        /// taken as relative from the application base 
        /// directory.
        /// </para>
        /// <para>
        /// The path specified must be a local file path, a URI is not supported.
        /// </para>
        /// </remarks>
        public static string ConvertToFullPath(string path)
        {
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            string baseDirectory = "";
            try
            {
                string applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory;
                if (applicationBaseDirectory != null)
                {
                    // applicationBaseDirectory may be a URI not a local file path
                    Uri applicationBaseDirectoryUri = new Uri(applicationBaseDirectory);
                    if (applicationBaseDirectoryUri.IsFile)
                    {
                        baseDirectory = applicationBaseDirectoryUri.LocalPath;
                    }
                }
            }
            catch
            {
                // Ignore URI exceptions & SecurityExceptions from SystemInfo.ApplicationBaseDirectory
            }

            if (!string.IsNullOrEmpty( baseDirectory ))
            {
                // Note that Path.Combine will return the second path if it is rooted
                return Path.GetFullPath(Path.Combine(baseDirectory, path));
            }
            return Path.GetFullPath(path);
        }
    }
}